//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//  NotificationChannelList.cs
//    
// Abstract:
//
//  A list of notification channels.
//  
//--

using System;
using System.Text;
using System.Diagnostics;
using System.Collections.Generic;
using Microsoft.OfficeCommunicationsServer.Applications.Common;

namespace Microsoft.EnhancedReminders
{
    /// <summary>
    /// Contains a list of notification channels.
    /// </summary>
    [Serializable()]
    public class NotificationChannelList
    {
        private List<NotificationChannel> _channels = new List<NotificationChannel>();

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="channelList">Channel list to copy</param>
        protected NotificationChannelList(NotificationChannelList channelList)
        {
            Debug.Assert(channelList != null, "channelList != null");

            foreach (NotificationChannel channel in channelList.Channels)
            {
                _channels.Add(channel.Copy());
            }
        }

        /// <summary>
        /// Ctor.  Populates the list of channels from a given (master) list of channels.
        /// </summary>
        /// <param name="channels">The master list of channels to initialize from.</param>
        public NotificationChannelList(IEnumerable<NotificationChannel> channels)
        {
            Debug.Assert(channels != null, "channels");

            foreach (NotificationChannel channel in channels)
            {
                CopyChannelToList(_channels, channel, /*isEnabled*/channel.IsEnabled);
            }
        }

        /// <summary>
        /// Determines if the specified channel list is equivalent to this channel list.
        /// </summary>
        /// <param name="channelList">Channel list to compare against</param>
        /// <remarks>
        /// This method verifies that the channel lists have the same enabled channel references and in the same order.
        /// </remarks>
        /// <returns>
        /// True if the channel lists are equivalent; false otherwise
        /// </returns>
        public virtual bool Equals(NotificationChannelList channelList)
        {
            Debug.Assert(channelList != null, "channelList != null");

            int j = 0;

            // Check the channels in this object against the specified channel list
            for (int i = 0; i < _channels.Count; i++)
            {
                if (_channels[i].IsEnabled)
                {
                    for (; j < channelList.Channels.Count; j++)
                    {
                        if (channelList.Channels[j].IsEnabled)
                        {
                            // Check that the two channels have the same channel reference
                            if (_channels[i].Id != channelList.Channels[j].Id)
                            {
                                return false;
                            }
                        }
                    }
                }
            }

            // Check the remaining channels in the specified channel list
            for (; j < channelList.Channels.Count; j++)
            {
                if (channelList.Channels[j].IsEnabled)
                {
                    // If any of the channels are enabled then the two sets of channels don't match
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Sync the channels list to the latest version of the master channel list.  This:
        ///   - removes channels that are no longer in the master list
        ///   - appends channels that have been added to the master list which are not in _channels
        ///   - updates the channel with the matching master channel data *except* for the IsEnabled
        ///     property which is preserved from the reference list.  Thus the channel order and 
        ///     IsEnabled property are preserved for the reference channels.
        /// </summary>
        /// <param name="masterChannels">The master channels list.</param>
        /// <param name="isJoinNow">Whether the channels are being synced to a join now list</param>
        /// <param name="appendUnseenChannels">
        /// True to add channels not in the reference list to the reference list.  This would be true for
        /// use in the UI but false for the server so as not to process channels unnecessarily.  When 
        /// channels are appended for the UI the IsEnabled will always be set to false.
        /// </param>
        public void SyncToMasterChannelsList(IEnumerable<NotificationChannel> masterChannels, bool isJoinNow, bool appendUnseenChannels)
        {
            Debug.Assert(masterChannels != null, "masterChannels");

            List<NotificationChannel> updatedChannels = new List<NotificationChannel>();
            
            // Create a dictionary for the master channels so we only have to traverse this list once.
            Dictionary<Guid, NotificationChannel> masterDictionary = new Dictionary<Guid,NotificationChannel>();
            foreach (NotificationChannel masterChannel in masterChannels)
            {
                masterDictionary.Add(masterChannel.Id, masterChannel);
            }

            // Find each reference channel in the master list and take a copy of it but with the 
            // reference channel's IsEnabled property.  The reference channel is not included in
            // the updated channels list if a corresponding entry in the master list is not found.
            foreach (NotificationChannel referenceChannel in _channels)
            {
                NotificationChannel masterChannel = null;
                if (!masterDictionary.TryGetValue(referenceChannel.Id, out masterChannel))
                {
                    continue;
                }
 
                masterDictionary.Remove(referenceChannel.Id); // Remove, so that remaining channels are new channels.
 
                if (String.IsNullOrEmpty(masterChannel.UserDefinedValue) ||
                    (isJoinNow && !masterChannel.AllowsJoinNow))
                {
                    continue; // Remove empty channels (so they don't appear in the UI) and non-join now channels if list is for join now.
                }

                CopyChannelToList(updatedChannels, masterChannel, /*isEnabled*/referenceChannel.IsEnabled);
            }

            // Optionally append any master channels that are not in the reference channel list to the end 
            // in the order they appear in the master list and with IsEnabled->false.
            if (appendUnseenChannels)
            {
                foreach (NotificationChannel masterChannel in masterDictionary.Values)
                {
                    if (String.IsNullOrEmpty(masterChannel.UserDefinedValue) ||
                        (isJoinNow && !masterChannel.AllowsJoinNow))
                    {
                        continue; // Remove empty channels (so they don't appear in the UI) and non-join now channels if list is for join now.
                    }

                    CopyChannelToList(updatedChannels, masterChannel, /*isEnabled*/false);
                }
            }

            _channels = updatedChannels;
        }

        /// <summary>
        /// Resolve the custom channels in this collection against the specified master channel list.
        /// </summary>
        /// <param name="masterChannelList">Master channel list</param>
        /// <remarks>
        /// Any enabled custom channel that's not in the master list will be disabled. Note, the master channel
        /// list can be null, in which case all custom channels will be disbaled.
        /// </remarks>
        public void ResolveCustomChannels(NotificationChannelList masterChannelList)
        {
            Dictionary<Guid, NotificationChannel> masterDictionary = null;

            foreach (NotificationChannel channel in _channels)
            {
                // Only need to resolve enabled custom channels
                if (channel.IsEnabled && channel is NotificationCustomChannel)
                {
                    if (masterChannelList != null && masterChannelList.Channels != null)
                    {
                        if (masterDictionary == null)
                        {
                            // Create dictionary from the master channel list to make searching easier
                            masterDictionary = new Dictionary<Guid, NotificationChannel>();

                            foreach (NotificationChannel masterChannel in masterChannelList.Channels)
                            {
                                masterDictionary.Add(masterChannel.Id, masterChannel);
                            }
                        }

                        NotificationChannel matchingMasterChannel = null;

                        if (masterDictionary.TryGetValue(channel.Id, out matchingMasterChannel))
                        {
                            // If the reference channel is in the master channel list copy over the user-defined values
                            channel.UserDefinedValue = matchingMasterChannel.UserDefinedValue;
                            channel.UserDefinedName = matchingMasterChannel.UserDefinedName;
                            channel.NotificationChannelType = matchingMasterChannel.NotificationChannelType;
                            continue;
                        }
                    }

                    // Disable custom channels that aren't in the master list
                    channel.IsEnabled = false;
                }
            }
        }

        /// <summary>
        /// Helper method to take a copy of a channel and add it to a list with the correct IsEnabled setting.
        /// </summary>
        /// <param name="channels">A list of channels to add the copied channel to.</param>
        /// <param name="channel">The channel to be copied into the list.</param>
        /// <param name="isEnabled">The target value of the IsEnabled property that the copied channel should have.</param>
        private static void CopyChannelToList(List<NotificationChannel> channels, NotificationChannel channel, bool isEnabled)
        {
            Debug.Assert(channels != null, "channels");
            Debug.Assert(channel != null, "channel");

            NotificationChannel copyChannel = channel.Copy();
            copyChannel.IsEnabled = isEnabled;
            channels.Add(copyChannel);
        }

        /// <summary>
        /// Creates a copy of a channel list instance.
        /// </summary>
        /// <returns></returns>
        public virtual NotificationChannelList Copy()
        {
            return new NotificationChannelList(Channels);
        }

        /// The list of reference channels which stores the channel order and whether the channel is
        /// enabled or not for a given rule or reminder.  New custom channels may have been added or
        /// removed by the user after this list is stored and so SyncToMasterChannelsList() must be
        /// called to sync up to the latest master channel list.
        public List<NotificationChannel> Channels
        {
            get
            {
                return _channels;
            }
        }

        /// <summary>
        /// Indicates if any of the channels in this list is enabled.
        /// </summary>
        public virtual bool IsEnabled
        {
            get
            {
                Debug.Assert(_channels != null, "_channels != null");

                foreach (NotificationChannel channel in _channels)
                {
                    if (channel.IsEnabled)
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        /// <summary>
        /// Log the channel info to the verbose trace logs.
        /// </summary>
        /// <param name="context">The logging context.</param>
        internal void LogTraceInfo(Context context)
        {
            Debug.Assert(context != null, "context");

            if (_channels == null)
            {
                return;
            }

            foreach (NotificationChannel channel in _channels)
            {
                channel.LogTraceInfo(context);
            }
        }

        /// <summary>
        /// Generate a list of channel IDs for the enabled channels for logging.
        /// </summary>
        internal string GetLogString()
        {
            if (_channels == null || _channels.Count == 0)
            {
                return String.Empty;
            }

            StringBuilder description = new StringBuilder();
            foreach (NotificationChannel channel in _channels)
            {
                if (channel.IsEnabled)
                {
                    if (description.Length > 0)
                    {
                        description.Append('+'); // Multiple channels.
                    }
                    description.Append(channel.Id);
                }
            }

            return description.ToString();
        }            
    }
}
